#include <stdlib.h>
#include <iostream>
#include <string.h>

using std::cout;
using std::endl;

/*
    在C++中，new 和 new[] 都是用于动态分配内存的操作符，但它们之间有一些重要的区别：

1、new
   - new 用于分配单个对象的内存空间，存储一个对象。
   - 会调用相应构造函数
   - 使用 new 分配的内存应该使用 delete 来释放。

2、new[]
   - new[] 用于分配数组的内存空间，存储一个数组。
   - 会多次调用相应的构造函数
   - 使用 new[] 分配的内存应该使用 delete[] 来释放。
   - 使用 new[] 为数组分配内存时，系统会在分配的内存块中存储数组的长度信息，
     以便在释放内存时能够正确地调用每个元素的析构函数。
   - 对于 类型 *ptr = new 类型[](); ptr都是指向开头第一个元素，尾大不掉
   
   在释放内存时，
   使用 delete 释放 new 分配的内存，使用 delete[] 释放 new[] 分配的内存，
   这样可以确保内存被正确释放，避免内存泄漏和未定义行为。
   如果该空间上存放的是类对象，则会自动调用它的析构函数
*/

/*
    new表达式工作步骤
    1. 调用名为operator new的标准库函数，分配足够大的原始的未类型化的内存，以保存指定类型的一个对象
    2. 运行该类型的一个构造函数初始化对象
    3. 返回指向新分配并构造的构造函数对象的指针

    delete表达式工作步骤
    1. 调用析构函数，回收对象中数据成员所申请的资源
    2. 调用名为operator delete的标准库函数释放该对象所用的内存
*/ 

/*
    在C++中，malloc 和 new 都用于动态分配内存，但它们之间有一些关键的区别：

1、malloc：
    - malloc 是C语言中的函数，用于在堆上分配一块指定大小的内存空间。
    - malloc 分配的内存空间是未初始化的，即其中的内容是未定义的，可能包含任意值。
    - 使用 malloc 分配的内存需要手动初始化，否则其中的数据可能是随机的。
    - 在C++中，尽管可以使用 malloc 分配内存，但不建议与C++对象一起使用，
     因为它不会调用对象的构造函数。
    int *ptr = (int*)malloc(sizeof(int)); // 未初始化的内存空间

2、new：
    - new 是C++中的操作符，用于在堆上分配一块指定大小的内存空间，并调用对象的构造函数进行初始化。
    - new 分配的内存空间会被初始化为对象的默认值（对于基本数据类型，会被初始化为0）。
    - 使用 new 分配的内存空间不需要手动初始化，对象的构造函数会自动被调用。
    - int *ptr = new int; // 初始化为0
    - int *ptr = new int(5); // 初始化为0
    - int *ptr = new int[]; // 初始化为0
    - int *ptr = new int[](); // 初始化为0
    - int *ptr = new int[](5); // 报错
    
    因此，使用 new 分配内存时，对象会被正确初始化，
    而使用 malloc 分配内存时，需要手动初始化，否则其中的数据是未定义的。
    在C++中，推荐使用 new 来动态分配内存，特别是用于管理类对象。
*/ 

class Test{
public:
    Test(int x)
    : _x(x)
    {
        cout<<"Test(int )"<<endl;
    }

    ~Test()
    {
        cout<<"~Test()"<<endl;
    }
private:
    int _x;
};

void test(){
    //原始的未初始化的空间
    int *pret=(int *)malloc(sizeof(int));
    cout<<*pret<<endl;
    //清零
    memset(pret,0,sizeof(int));
    *pret=10;
    cout<<*pret<<endl;
    //...正常使用
    //...
    free(pret);//回收堆空间
    pret=NULL;
}

void test1(){
    //new是C++中申请堆空间的方式，并且可以赋初值
    int *pInt = new int(10);
    cout << *pInt <<endl; //10

    *pInt=20;
    cout << *pInt <<endl; //20
    
    //...正常使用
    //...
    delete pInt;
    pInt=nullptr;

}

void test2(){
    int *pInt = new int[10]();
    //int *pInt = new int[10](5); //报错
    pInt[0]=10;
    pInt[1]=20;
    //
    //
    //
    cout<<pInt<<endl; //数组第一个元素的地址，0x560dc0cc6e70
    cout<<&pInt[0]<<endl; //数组第一个元素的地址，0x560dc0cc6e70
    cout<<&pInt[1]<<endl; //数组第二个元素的地址，0x560dc0cc6e74

    cout<<*pInt<<endl; //10
    cout << pInt[0] <<endl; //10
    cout << pInt[1] <<endl; //20
    cout << pInt[2]<<endl; //0
    delete []pInt;
}

void test3(){
    //char* str = (char*)malloc(100 * sizeof(char));
    char *ptr = new char[10];
    ptr[0]='h';
    cout<<*ptr<<endl;   //h
    cout<<ptr<<endl;    //h
    
    const char *ptr1 = "world";
    strcpy(ptr,ptr1);
    cout<<*ptr<<endl;   //w
    cout<<ptr<<endl;    //world
    
    delete []ptr;
/*
    在C++中，当你使用 new char[10] 分配一个包含10个 char 元素的数组时，
    返回的指针 str 确实是指向数组的第一个元素的地址，而不是整个数组的地址。
    这意味着 str 指针仅管理数组的起始位置。
    你可以通过对 str 指针进行偏移来访问数组的其他元素。
    例如，你可以使用 str[0] 访问第一个元素，str[1] 访问第二个元素，
    依此类推，直到 str[9] 访问最后一个元素。
    因此，str 指针并不直接管理整个10字节的空间，而是只管理数组的起始位置。
*/ 
}

void test4(){
    //Test *ptr = new Test[3](5); //报错
    Test *ptr = new Test[3]{5,6,7};
    delete []ptr;
    //3次构造，3次析构
}

int main()
{
    test();
    cout<<endl;
    test1();
    cout<<endl;
    test2();
    cout<<endl;
    test3();
    cout<<endl;
    test4();
    return 0;
}

